package org.flexthinker.binding.bean
{
    import org.flexthinker.binding.conversion.support.PropertyConverter;
    import org.flexthinker.binding.value.support.MockBean;
    import org.hamcrest.assertThat;
    import org.hamcrest.core.not;
    import org.hamcrest.core.throws;
    import org.hamcrest.object.equalTo;
    import org.hamcrest.object.instanceOf;

    public class BindingTest
    {
        private var binding:Binding;
        private var mockBean:MockBean;
        private var targetBean:MockBean;

        private var bindingListener:BindingListenerCounter;

        public function BindingTest()
        {
        }

        [Before]
        public function setUp():void
        {
            mockBean = new MockBean();
            targetBean = new MockBean();
            bindingListener = new BindingListenerCounter();
        }

        [Test]
        public function testBindingInitialization():void
        {
            mockBean.simpleString = "A";
            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("simpleString"),
                    targetBean, BeanProperty.create("bindableProperty"));

            assertThat("Source property should not be changed initially", mockBean.simpleString, equalTo("A"));
            assertThat("Target property should not be changed initially", targetBean.bindableProperty, equalTo("B"));

            assertThat("BindingListener shouldn't get called before calling bind", bindingListener.counter, equalTo(0));

            assertThat(function ():void
                        {
                            binding = new Binding(null, BeanProperty.create("simpleString"),
                                                    targetBean, BeanProperty.create("bindableProperty"));
                        },
                        throws(instanceOf(ArgumentError)));
            assertThat(function ():void
                        {
                            binding = new Binding(mockBean, null,
                                                    targetBean, BeanProperty.create("bindableProperty"));
                        },
                        throws(instanceOf(ArgumentError)));
            assertThat(function ():void
                        {
                            binding = new Binding(mockBean, BeanProperty.create("simpleString"),
                                                    null, BeanProperty.create("bindableProperty"));
                        },
                        throws(instanceOf(ArgumentError)));
            assertThat(function ():void
                        {
                            binding = new Binding(mockBean, BeanProperty.create("simpleString"),
                                                    targetBean, null);
                        },
                        throws(instanceOf(ArgumentError)));
        }

        [Test]
        public function testBindingWhenSourceChanges():void
        {
            mockBean.objectHolder = new MockBean();
            mockBean.objectHolder.simpleString = "A";

            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("objectHolder.simpleString"),
                    targetBean, BeanProperty.create("bindableProperty"));
            binding.addBindingListener(bindingListener);

            binding.bind();

            assertThat("BindingListener should be called right after binding", bindingListener.counter, equalTo(1));
            assertThat("BindingListener.bindingBecameBound should be called", bindingListener.boundCounter, equalTo(1));

            mockBean.objectHolder.simpleString = "X";

            assertThat("BindingListener should be called when source changes", bindingListener.counter, equalTo(2));
            assertThat("BindingListener.sourceChanged should be called", bindingListener.sourceChangedCounter,
                    equalTo(1));
            assertThat("Binding shouldn't apply any sync", mockBean.simpleString,
                    not(equalTo(targetBean.bindableProperty)));
        }

        [Test]
        public function testBindingWhenTargetChanges():void
        {
            mockBean.simpleString = "A";
            targetBean.objectHolder = new MockBean();
            targetBean.objectHolder.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("simpleString"),
                    targetBean, BeanProperty.create("objectHolder.bindableProperty"));
            binding.addBindingListener(bindingListener);

            binding.bind();

            targetBean.objectHolder.bindableProperty = "Y";

            assertThat("BindingListener should be called when target changes", bindingListener.counter, equalTo(2));
            assertThat("BindingListener.sourceChanged", bindingListener.targetChangedCounter, equalTo(1));
            assertThat("Binding shouldn't sync anything", mockBean.simpleString,
                    not(equalTo(targetBean.bindableProperty)));
        }

        [Test]
        public function testUnbind():void
        {
            mockBean.simpleString = "A";
            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("simpleString"),
                    targetBean, BeanProperty.create("bindableProperty"));
            binding.addBindingListener(bindingListener);

            binding.bind();

            mockBean.simpleString = "X";
            targetBean.bindableProperty = "Y";

            assertThat("BindingListeners not called properly", bindingListener.counter, equalTo(3));
            assertThat("BindingListeners not called properly on source change", bindingListener.sourceChangedCounter,
                    equalTo(1));
            assertThat("BindingListeners not called properly on target change", bindingListener.targetChangedCounter,
                    equalTo(1));

            binding.unbind();

            mockBean.simpleString = "W";
            targetBean.bindableProperty = "Q";

            assertThat("BindingListener should b called once after unbind", bindingListener.counter, equalTo(4));
            assertThat("BindingListener.sourceChanged shouldn't be called ", bindingListener.sourceChangedCounter,
                    equalTo(1));
            assertThat("BindingListener.targetChanged shouldn't be called ", bindingListener.targetChangedCounter,
                    equalTo(1));

        }

        [Test]
        public function testSave():void
        {
            mockBean.objectHolder = new MockBean();
            mockBean.objectHolder.simpleString = "A";

            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("objectHolder.simpleString"),
                    targetBean, BeanProperty.create("bindableProperty"));
            binding.addBindingListener(bindingListener);
            binding.bind();

            binding.saveAndNotify();
            assertThat("Target value should be written in the source", mockBean.objectHolder.simpleString, equalTo(targetBean.bindableProperty));
            assertThat("Target value should remain unchanged", targetBean.bindableProperty, equalTo("B"));

            assertThat("sourceChanged should be called by binding listener",bindingListener.sourceChangedCounter, equalTo(1));
            assertThat("targetChanged should be called by binding listener", bindingListener.targetChangedCounter, equalTo(0));
            assertThat("binding listener should be called after refresh()", bindingListener.counter, equalTo(3));

            assertThat("synced should be called by binding listener on susccessfull sync", bindingListener.syncedCounter, equalTo(1) );
        }

        [Test]
        public function testRefresh():void
        {
            mockBean.objectHolder = new MockBean();
            mockBean.objectHolder.simpleString = "A";

            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("objectHolder.simpleString"),
                    targetBean, BeanProperty.create("bindableProperty"));
            binding.addBindingListener(bindingListener);
            binding.bind();

            assertThat("binding listener should be called after bind", bindingListener.counter, equalTo(1));

            binding.refreshAndNotify();
            assertThat("Source value should be written in the target", targetBean.bindableProperty, equalTo(mockBean.objectHolder.simpleString));
            assertThat("Source value should remain unchanged", mockBean.objectHolder.simpleString, equalTo("A"));

            assertThat("sourceChanged should be called by binding listener",bindingListener.sourceChangedCounter, equalTo(0));
            assertThat("targetChanged should be called by binding listener", bindingListener.targetChangedCounter, equalTo(1));
            assertThat("binding listener should be called after refresh()", bindingListener.counter, equalTo(3));

            assertThat("synced should be called by binding listener on susccessful sync", bindingListener.syncedCounter, equalTo(1) );
        }

        [Test]
        public function testConversion():void
        {
            mockBean.objectHolder = new MockBean();
            mockBean.objectHolder.simpleString = "A";

            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("objectHolder"),
                    targetBean, BeanProperty.create("bindableProperty"));
            binding.setConverter( new PropertyConverter(
                                        function convertForward( value:MockBean):String
                                        {
                                            return value.simpleString;
                                        },
                                        function convertReverse(value:String):MockBean
                                        {
                                            var b:MockBean = new MockBean();
                                            b.simpleString = value;
                                            return b;
                                        }
                                ));
            binding.bind();

            binding.refresh();

            assertThat("convertion hasn't been applied on 'refresh'", targetBean.bindableProperty, equalTo(mockBean.objectHolder.simpleString) );
            assertThat("converted value is wrong", targetBean.bindableProperty, equalTo("A") );

            targetBean.bindableProperty = "CCC";

            binding.save();

            assertThat("convertion hasn't been applied on 'save'", mockBean.objectHolder.simpleString, equalTo(targetBean.bindableProperty));
            assertThat("converted value is wrong", mockBean.objectHolder.simpleString, equalTo("CCC"));

            assertThat("setConverter shouldn't work after bind() is called",
                        function ():void
                        {
                            binding.setConverter( new PropertyConverter(null, null) );
                        },
                        throws( instanceOf(Error)));

            binding.unbind();
        }

        //negative tests
        [Test]
        public function testBindCanBeCalledOnce():void
        {
            mockBean.simpleString = "A";
            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("simpleString"),
                    targetBean, BeanProperty.create("bindableProperty"));

            binding.bind();

            assertThat("Binding.bind() is allowed to be called only once",
                    function():void
                    {
                        binding.bind();
                    }, throws(instanceOf(Error)));
        }

        [Test]
        public function testUnbindCanBeCalledOnce():void
        {
            mockBean.simpleString = "A";
            targetBean.bindableProperty = "B";

            binding = new Binding(mockBean, BeanProperty.create("simpleString"),
                    targetBean, BeanProperty.create("bindableProperty"));

            assertThat("Binding.unbind() can't be called before Binding.bind()",
                    function():void
                    {

                        binding.unbind();
                    }, throws(instanceOf(Error)));

            binding.bind();
            binding.unbind();

            assertThat("Binding.unbind() can't be called twice",
                    function():void
                    {

                        binding.unbind();
                    }, throws(instanceOf(Error)));
        }

        [Test]
        public function testSaveFailure():void
        {

        }

        [Test]
        public function testRefreshFailure():void
        {

        }

    }
}
